#include "http_response.h"
#include "http_state.h"
#include "logger/logger.h"
#include <cassert>
#include <fcntl.h>
#include <unistd.h>

namespace zh
{
    const std::unordered_map<std::string, std::string> HttpResponse::s_suffixTypes = {
        {".html", "text/html"},          {".xml", "text/xml"},          {".xhtml", "application/xhtml+xml"},
        {".txt", "text/plain"},          {".rtf", "application/rtf"},   {".pdf", "application/pdf"},
        {".word", "application/nsword"}, {".png", "image/png"},         {".gif", "image/gif"},
        {".jpg", "image/jpeg"},          {".jpeg", "image/jpeg"},       {".au", "audio/basic"},
        {".mpeg", "video/mpeg"},         {".mpg", "video/mpeg"},        {".avi", "video/x-msvideo"},
        {".gz", "application/x-gzip"},   {".tar", "application/x-tar"}, {".css", "text/css "},
        {".js", "text/javascript "},
    };

    const std::unordered_map<int, std::string> HttpResponse::s_codeStatus = {
        {HttpStateCode::OK, "OK"},
        {HttpStateCode::BAD_REQUEST, "Bad Request"},
        {HttpStateCode::FORBIDDEN, "Forbidden"},
        {HttpStateCode::NOT_FOUND, "Not Found"},
    };

    const std::unordered_map<int, std::string> HttpResponse::s_codePath = {
        {HttpStateCode::BAD_REQUEST, "/400.html"},
        {HttpStateCode::FORBIDDEN, "/403.html"},
        {HttpStateCode::NOT_FOUND, "/404.html"},
    };

    HttpResponse::HttpResponse() {}

    HttpResponse::~HttpResponse()
    {
        unmapFile();
    }

    void HttpResponse::unmapFile()
    {
        if (m_mmapFile)
        {
            munmap(m_mmapFile, m_mmapFileState.st_size);
            m_mmapFile = nullptr;
        }
    }

    void
    HttpResponse::initialize(const std::string& sourceDirectory, const std::string& path, bool isKeepAlive, int code)
    {
        assert(!sourceDirectory.empty());
        if (m_mmapFile)
        {
            unmapFile();
        }
        m_code            = code;
        m_isKeepAlive     = isKeepAlive;
        m_path            = path;
        m_sourceDirectory = sourceDirectory;
        m_mmapFile        = nullptr;
        m_mmapFileState   = {0};
    }

    void HttpResponse::makeResponse(Buffer& buffer)
    {
        std::string filePath = m_sourceDirectory + m_path;

        auto isBadFile        = [&]() -> bool { return stat(filePath.data(), &m_mmapFileState) < 0; };
        auto isDirectory      = [&]() -> bool { return S_ISDIR(m_mmapFileState.st_mode); };
        auto noReadPermission = [&]() -> bool { return !(m_mmapFileState.st_mode & S_IROTH); };

        if (isBadFile() || isDirectory())
        {
            LOG_ERROR("file:{}, isBad: {}, isDir: {}", filePath, isBadFile(), isDirectory());
            m_code = HttpStateCode::NOT_FOUND;
        }
        else if (noReadPermission())
        {
            m_code = HttpStateCode::FORBIDDEN;
        }
        else if (m_code == -1)
        {
            m_code = HttpStateCode::OK;
        }
        LOG_INFO("makeResponse: {}, code: {}", m_sourceDirectory + m_path, m_code);
        errorHTML();
        addLine(buffer);
        addHeader(buffer);
        addContent(buffer);
    }

    void HttpResponse::errorHTML()
    {
        if (s_codePath.count(m_code) == 1)
        {
            m_path = s_codePath.find(m_code)->second;
            stat((m_sourceDirectory + m_path).data(), &m_mmapFileState);
        }
    }

    std::string HttpResponse::getFileType() const
    {
        std::size_t index = m_path.find_last_of('.');
        if (index != std::string::npos)
        {
            std::string suffix = m_path.substr(index);
            if (s_suffixTypes.count(suffix) == 1)
            {
                return s_suffixTypes.find(suffix)->second;
            }
        }
        return "text/plain";
    }

    char* HttpResponse::file()
    {
        return m_mmapFile;
    }

    size_t HttpResponse::fileLength() const
    {
        return m_mmapFileState.st_size;
    }

    void HttpResponse::addLine(Buffer& buffer)
    {
        std::string status;
        if (s_codeStatus.count(m_code) == 1)
        {
            status = s_codeStatus.find(m_code)->second;
        }
        else
        {
            m_code = HttpStateCode::BAD_REQUEST;
            status = s_codeStatus.find(m_code)->second;
        }
        buffer.append("HTTP/1.1 " + std::to_string(m_code) + " " + status + "\r\n");
    }

    void HttpResponse::addHeader(Buffer& buffer)
    {
        buffer.append("Conncetion: ");
        if (m_isKeepAlive)
        {
            buffer.append("keep-alive\r\n");
            buffer.append("keep-alive: max=6, timeout=120\r\n");
        }
        else
        {
            buffer.append("close\r\n");
        }
        buffer.append("Content-type: " + getFileType() + "\r\n");
    }

    void HttpResponse::addContent(Buffer& buffer)
    {
        int sourceFD = open((m_sourceDirectory + m_path).data(), O_RDONLY);
        if (sourceFD < 0)
        {
            LOG_ERROR("addContent: sourceFD < 0");
            errorContent(buffer, "File not found");
            return;
        }
        void* mmRet = mmap(0, m_mmapFileState.st_size, PROT_READ, MAP_PRIVATE, sourceFD, 0);
        if (mmRet == MAP_FAILED)
        {
            LOG_ERROR("mmRet == MAP_FAILED, {}", m_mmapFileState.st_size);
            errorContent(buffer, "File not found");
        }
        m_mmapFile = static_cast<char*>(mmRet);
        close(sourceFD);
        buffer.append("Content-Length: " + std::to_string(m_mmapFileState.st_size) + "\r\n\r\n");
    }

    void HttpResponse::errorContent(Buffer& buffer, const std::string& message)
    {
        std::string body;
        std::string status;
        body += "<html><title>Error</title>";
        body += "<body bgcolor=\"ffffff\">";
        if (s_codeStatus.count(m_code) == 1)
        {
            status = s_codeStatus.find(m_code)->second;
        }
        else
        {
            status = "Bad Request";
        }
        body += std::to_string(m_code) + ":" + status + "\n";
        body += "<p>" + message + "</p>";
        body += "<hr><em>Webserver</em></body></html>";
        buffer.append("Content-length: " + std::to_string(body.size()) + "\r\n\r\n");
        buffer.append(body);
    }

} // namespace zh